package tech

import (
	"slices"
	"ssp/set"
)

func removeRepetition[E comparable](s, removed []E) []E {
	r := []E{}
	for _, e := range s {
		if !slices.Contains(removed, e) {
			r = append(r, e)
		}
	}
	return r
}

func getCellsInPathFromGroup(node *ChainNode) []*Cell {
	r := []*Cell{}
	for n := node; n != nil; n = n.pre {
		r = append(r, n.item.getCells()...)
	}
	return r
}

func (g *Grid) groupSeenGroups(node *ChainNode) (groups []GroupItem) {
	cand := node.item.cand
	cells := g.candCellsSeenCells(cand, node.item.getCells())
	//too strict
	//cells = removeRepetition(cells, getCellsInPathFromGroup(node))

	for _, cell := range cells {
		group := GroupItem{cand, [9]*Cell{cell, nil, nil}}
		groups = append(groups, group)
	}

	boxCells := make([][]*Cell, 9)
	for _, cell := range cells {
		boxCells[cell.boxNum] = append(boxCells[cell.boxNum], cell)
	}
	startRow := g.boxes[node.item.group[0].boxNum].cells[0].rowNum
	startCol := g.boxes[node.item.group[0].boxNum].cells[0].colNum

	for _, cells := range boxCells {
		for rowNum := startRow; rowNum < startRow+3; rowNum++ {
			group := GroupItem{cand: cand}
			j := 0
			for _, cell := range cells {
				if cell.rowNum == rowNum {
					group.group[j] = cell
					j++
				}
			}
			if j > 1 {
				groups = append(groups, group)
			}
		}

		for colNum := startCol; colNum < startCol+3; colNum++ {
			group := GroupItem{cand: cand}
			j := 0
			for _, cell := range cells {
				if cell.colNum == colNum {
					group.group[j] = cell
					j++
				}
			}
			if j > 1 {
				groups = append(groups, group)
			}
		}
	}

	return
}

func (g *Grid) groupedXCycleGetStrongs(node *ChainNode) []GroupItem {
	strongs := []GroupItem{}
	from := node.item.getCells()

	for _, group := range g.groupSeenGroups(node) {
		cells := group.getCells()
		cells = append(cells, from...)
		houses := g.getSameHouses(cells)
		if isOnlyInHouses(node.item.cand, cells, houses) {
			strongs = append(strongs, group)
		}
	}

	return strongs
}

func (g *Grid) canRemoveGroup(items []GroupItem) (rmItems []GroupItem) {
	head := items[1]
	tail := items[len(items)-1]
	cand := head.cand
	cells := append(head.getCells(), tail.getCells()...)
	if ok, rmCells := g.canRemoveSeenCands(set.NewSet(cand), cells); ok {
		for _, cell := range rmCells {
			rmItems = append(rmItems, GroupItem{cand, [9]*Cell{cell}})
		}
	}
	return
}

func twoGroupCloseCycle(head, tail GroupItem) bool {
	return isSameHouse(append(head.getCells(), tail.getCells()...))
}

func (s *Solve) GroupXCycle3(g *Grid) (used bool, results []*Result) {
	c := Chain{
		g:            g,
		strategyName: "Grouped X-Cycle",
		getWeaks:     g.groupSeenGroups,
		getStrongs:   g.groupedXCycleGetStrongs,
		closeCycle:   twoGroupCloseCycle,
		canRemove:    g.canRemoveGroup,
	}

	return c.ExecStrategy3()
}
